/*************************************************************************
 *					SRLOS TEAM ALL RIGHT RESERVED
 *@filename		inthw.c
 *@autor		bloceanc
 *@date			08/07/2010
 *@note			manager hardware interrupt
 ************************************************************************/

// s3c2440A interrupt manager
#include "commdef.h"
#include "int.h"

/* interrupt information table */
int_data s_int_table[32]; // interrupt table
int_data s_subint_table[15]; // sub source interrupt table

/*
 * initialize interrupt table.
 */
void int_initialize_table(void)
{
	unsigned int i;
	int_data *table = s_int_table;
	for(i = 0; i < 32; i++)
	{
		(table + i)->type = i;
		(table + i)->issubsrc = 0;
		(table + i)->parent_type = 0xff; /* 0xff means no parent */
		(table + i)->mask = 1<<i;
		(table + i)->service = NULL;
	}

	// set sub sources.
	// I hit these things, boring me....
	table = s_subint_table;
	i = 0;
	(table + i)->type = INT_RXD0;
	(table + i)->issubsrc = 1;
	(table + i)->parent_type = INT_UART0;
	(table + i)->mask = 1<<(INT_RXD0-INT_RXD0);
	(table + i)->service = NULL;

	i++;
	(table + i)->type = INT_TXD0;
	(table + i)->issubsrc = 1;
	(table + i)->parent_type = INT_UART0;
	(table + i)->mask = 1<<(INT_TXD0-INT_RXD0);
	(table + i)->service = NULL;

	i++;
	(table + i)->type = INT_ERR0;
	(table + i)->issubsrc = 1;
	(table + i)->parent_type = INT_UART0;
	(table + i)->mask  = 1<<(INT_ERR0-INT_RXD0);
	(table + i)->service = NULL;

	i++;
	(table + i)->type = INT_RXD1;
	(table + i)->issubsrc = 1;
	(table + i)->parent_type = INT_UART1;
	(table + i)->mask = 1<<(INT_RXD1-INT_RXD0);
	(table + i)->service = NULL;
	
	i++;
	(table + i)->type = INT_TXD1;
	(table + i)->issubsrc = 1;
	(table + i)->parent_type = INT_UART1;
	(table + i)->mask = 1<<(INT_TXD1-INT_RXD0);
	(table + i)->service = NULL;
	
	i++;
	(table + i)->type = INT_ERR1;
	(table + i)->issubsrc = 1;
	(table + i)->parent_type = INT_UART1;
	(table + i)->mask = 1<<(INT_ERR1-INT_RXD0);
	(table + i)->service = NULL;
	
	i++;
	(table + i)->type = INT_RXD2;
	(table + i)->issubsrc = 1;
	(table + i)->parent_type = INT_UART2;
	(table + i)->mask = 1<<(INT_RXD2-INT_RXD0);
	(table + i)->service = NULL;
	
	i++;
	(table + i)->type = INT_TXD2;
	(table + i)->issubsrc = 1;
	(table + i)->parent_type = INT_UART2;
	(table + i)->mask = 1<<(INT_TXD2-INT_RXD0);
	(table + i)->service = NULL;
	
	i++;
	(table + i)->type = INT_ERR2;
	(table + i)->issubsrc = 1;
	(table + i)->parent_type = INT_UART2;
	(table + i)->mask = 1<<(INT_ERR2-INT_RXD0);
	(table + i)->service = NULL;
	
	i++;
	(table + i)->type = INT_TC;
	(table + i)->issubsrc = 1;
	(table + i)->parent_type = INT_ADC;
	(table + i)->mask = 1<<(INT_TC-INT_RXD0);
	(table + i)->service = NULL;
	
	i++;
	(table + i)->type = INT_ADC_S;
	(table + i)->issubsrc = 1;
	(table + i)->parent_type = INT_ADC;
	(table + i)->mask = 1<<(INT_ADC_S-INT_RXD0);
	(table + i)->service = NULL;
	
	i++;
	(table + i)->type = INT_CAM_C;
	(table + i)->issubsrc = 1;
	(table + i)->parent_type = INT_CAM;
	(table + i)->mask = 1<<(INT_CAM_C-INT_RXD0);
	(table + i)->service = NULL;
	
	i++;
	(table + i)->type = INT_CAM_P;
	(table + i)->issubsrc = 1;
	(table + i)->parent_type = INT_CAM;
	(table + i)->mask = 1<<(INT_CAM_P-INT_RXD0);
	(table + i)->service = NULL;
	
	i++;
	(table + i)->type = INT_WDT;
	(table + i)->issubsrc = 1;
	(table + i)->parent_type = INT_WDT_AC97;
	(table + i)->mask = 1<<(INT_WDT-INT_RXD0);
	(table + i)->service = NULL;
	
	i++;
	(table + i)->type = INT_AC97;
	(table + i)->issubsrc = 1;
	(table + i)->parent_type = INT_WDT_AC97;
	(table + i)->mask = 1<<(INT_AC97-INT_RXD0);
	(table + i)->service = NULL;
}

/*
 * enable a type of interrupt 
 *@param	interrupt type
 *@note		this function will set mask for specific type of interrupt
 *		the interrupt will not be seviced which has been set mask
 */
void int_enable_interrupt(int_source type, irq_service service)
{
	if(type >= INT_RXD0)
	{
		int idx = type-INT_RXD0;
		// this is sub source interrupt.
		s_subint_table[idx].service = service;
		R_SUBINTMASK &= ~s_subint_table[idx].mask;
		R_INTMASK &= ~s_int_table[s_subint_table[idx].parent_type].mask;
	}
	else
	{
		s_int_table[type].service = service;
		R_INTMASK &= ~s_int_table[type].mask;
	}
}

/*
 * diable a type of interrupt
 */
void int_disable_interrupt(int_source type)
{
	if(type >= INT_RXD0)
	{
		int idx = type-INT_RXD0;
		R_SUBINTMASK |= s_subint_table[idx].mask;
	}
	else
	{
		R_INTMASK |= s_int_table[type].mask;
	}
}

/*
 * get interrup type of requested
 */
unsigned int int_get_requesttype(void)
{
	return R_INTOFFSET;
}

/*
 * get sub interrupt source type of requested
 */
unsigned int int_get_subrequesttype(int_source maintype)
{
	unsigned int val = R_SUBSRCPND;
	switch(maintype)
	{
		case INT_UART0:
			{
				val &= 0x7; // low 3 bit.
				if(val&0x1)
				{
					return INT_RXD0;
				}
				if(val&0x2)
				{
					return INT_TXD0;
				}
				if(val&0x4)
				{
					return INT_ERR0;
				}
			}
			break;
		case INT_UART1:
			{
				val = (val>>3)&0x7; 
				if(val&0x1)
				{
					return INT_RXD1;
				}
				if(val&0x2)
				{
					return INT_TXD1;
				}
				if(val&0x4)
				{
					return INT_ERR1;
				}
			}
			break;
		case INT_UART2:
			{
				val = (val>>6)&0x7;
				if(val&0x1)
				{
					return INT_RXD2;
				}
				if(val&0x2)
				{
					return INT_TXD2;
				}
				if(val&0x4)
				{
					return INT_ERR2;
				}
			}
			break;
		case INT_ADC:
			{
				val = (val>>9)&0x3;
				if(val&0x1)
				{
					return INT_TC;
				}
				if(val&0x2)
				{
					return INT_ADC_S;
				}
			}
			break;
		case INT_CAM:
			{
				val = (val>>11)&0x3;
				if(val&0x1)
				{
					return INT_CAM_C;
				}
				if(val&0x2)
				{
					return INT_CAM_P;
				}
			}
			break;
		case INT_WDT_AC97:
			{
				val = (val>>13)&0x3;
				if(val&0x1)
				{
					return INT_WDT;
				}
				if(val&0x2)
				{
					return INT_AC97;
				}
			}
			break;
		default:
			return INT_TYPE_ERROR; // this means error occures
	}
	return INT_TYPE_ERROR; // this means error occures
}

/*
 * reset interrupt request
 * 		NOTE:
 * 			In S3C2440, bit write 1 means to clear it in PND registers, also, need SUBSRCPND should ahead of 
 * 			SRCPND, and SRCPND should ahead of INTPND.
 */
void int_request_reset(int_source type)
{
	if(type >= INT_RXD0)
	{
		int idx = type-INT_RXD0;
		R_SUBSRCPND = s_subint_table[idx].mask;
		R_SRCPND = s_int_table[s_subint_table[idx].parent_type].mask;
		R_INTPND = s_int_table[s_subint_table[idx].parent_type].mask;
	}
	else
	{
		R_SRCPND = s_int_table[type].mask;
		R_INTPND = s_int_table[type].mask;
	}
}

/*
 *  IRQ handler
 */
void int_irq_handler(void)
{
	int_source req_type = int_get_requesttype();
	if(s_int_table[req_type].issubsrc == 1)
	{
		// a sub source interrupt occured
		int_source sub_type = int_get_subrequesttype(req_type);
		if(sub_type == INT_TYPE_ERROR)
		{
			// we just hold system down
			while(1);
		}
		// call service handle.^_^
		// WARNNING:	we should reset interrupt at the right time in service handler!
		if(s_subint_table[sub_type].service)
			s_subint_table[sub_type].service();
	}
	else
	{
		s_int_table[req_type].service();
	}
}


